#include <string.h>
#include <stdio.h>
#include "memsim.h"

//#define MEM_CACHE_SIZE 1024
//#define MEM_CACHE_ASSOC 1

#define MEM_CACHE_SIZE (1024*1024*4)
#define MEM_CACHE_ASSOC 1

unsigned int tag_s[MEM_CACHE_SIZE][MEM_CACHE_ASSOC];
int mem_s[MEM_CACHE_SIZE][MEM_CACHE_ASSOC];
unsigned int timestamp[MEM_CACHE_SIZE][MEM_CACHE_ASSOC];

unsigned int time = 0;
int get_assoc(unsigned int addr) {
	int idx = addr % MEM_CACHE_SIZE;
	int i;

	time += 1;
	unsigned int low_time = timestamp[idx][0];
	int low_idx = 0;
	for ( i = 0; i < MEM_CACHE_ASSOC; i++ ) {
		if ( tag_s[idx][i] == addr ) return i;
		if ( timestamp[idx][i] < low_time) {
			low_idx = i;
			low_time = timestamp[idx][i];
		}
	}
	return low_idx - 1; // so as to not have -0
}

void update_timestamp(int idx, int assoc) {
	timestamp[idx][assoc] = time;
}

void mem_init() {
	memset(tag_s, 0, sizeof(tag_s));
	memset(mem_s, 0, sizeof(mem_s));
	memset(timestamp, 0, sizeof(timestamp));
}

int commit_assoc;
unsigned int commit_addr;
unsigned int commit_value;

unsigned int total_loads = 0;
unsigned int load_misses = 0;
unsigned short mem_LHU(unsigned int addr) {
	unsigned int addra = addr << 2;
	unsigned char addrb = addr & 0b10;

	int idx = addra % MEM_CACHE_SIZE;

	int assoc = get_assoc(addra);
	if ( assoc < 0 ) {
		load_misses++;

		assoc = -1 * (assoc + 1);
		commit_assoc = assoc;
		commit_addr = addra;
		commit_value = 0;
		
		//TODO: report cache miss
		return 0x0;
	} else {
		update_timestamp(idx, assoc);
		union {
			unsigned int i;
			unsigned short c[2];
		} ita;
		ita.i = mem_s[idx][assoc];
		commit_assoc = -1;
		return ita.c[addrb>>1];
	}
}
char mem_LB(unsigned int addr) {
	unsigned int addra = addr << 2;
	unsigned char addrb = addr & 0b11;

	int idx = addra % MEM_CACHE_SIZE;

	int assoc = get_assoc(addra);
	if ( assoc < 0 ) {
		load_misses++;

		assoc = -1 * (assoc + 1);
		commit_assoc = assoc;
		commit_addr = addra;
		commit_value = 0;
		
		//TODO: report cache miss
		return 0x0;
	} else {
		update_timestamp(idx, assoc);
		union {
			unsigned int i;
			unsigned char c[4];
		} ita;
		ita.i = mem_s[idx][assoc];
		commit_assoc = -1;
		return ita.c[addrb];
	}
}

void mem_clear() {
	commit_assoc = -1;
}
void mem_commit() {
	if ( commit_assoc < 0 ) return;

	int idx = commit_addr % MEM_CACHE_SIZE;
	tag_s[idx][commit_assoc] = commit_addr;
	mem_s[idx][commit_assoc] = commit_value;
	update_timestamp(idx, commit_assoc);
}

unsigned int total_writes = 0;
unsigned int write_misses = 0;
int mem_LW (unsigned int addr) {
//	if ( addr & 0b11 ) fprintf(stderr, "unaligned LW(%x)?\n", addr );
	addr = addr >> 2;
	int idx = addr % MEM_CACHE_SIZE;
	total_loads++;

	int assoc = get_assoc(addr);
	if ( assoc < 0 ) {
		load_misses++;

		//TODO: report cache miss

		assoc = -1 * (assoc + 1);
		commit_assoc = assoc;
		commit_addr = addr;
		commit_value = 0;

		return 0;
		//return 0x0;
	} else {
		//update_timestamp(idx, assoc);
		commit_assoc = -1;
		return mem_s[idx][assoc];
	}
}

void mem_SB (unsigned int addr, signed char value) {
	total_writes ++;
	unsigned int addra = addr >> 2;
//	if ( addr & 0b11 ) fprintf(stderr, "unaligned SW(%x)?\n", addr );
	int idx = addra % MEM_CACHE_SIZE;
	unsigned char addrb = (addr % 4);
	
	union {
		unsigned int i;
		signed short c[4];
	} ita;

	int assoc = get_assoc(addra);
	if ( assoc < 0 ) {
		write_misses ++;
		//TODO: report cache miss
//		fprintf(stderr, "cache miss during SW!\n" );

		assoc = -1 * (assoc + 1);
		commit_assoc = assoc;
		commit_addr = addra;
		ita.i = 0;
		ita.c[addrb] = value;
		commit_value = ita.i;
		
//		tag_s[idx][assoc] = addr;
//		update_timestamp(idx, assoc);

	} else {
		commit_assoc = -1;
		ita.i = mem_s[idx][assoc];
		ita.c[addrb] = value;
		mem_s[idx][assoc] = ita.i;
	}
}
void mem_SH (unsigned int addr, signed short value) {
	total_writes ++;
	unsigned int addra = addr >> 2;
//	if ( addr & 0b11 ) fprintf(stderr, "unaligned SW(%x)?\n", addr );
	int idx = addra % MEM_CACHE_SIZE;
	unsigned char addrb = (addr % 4)/2;
	
	union {
		unsigned int i;
		signed short s[2];
	} ita;

	int assoc = get_assoc(addra);
	if ( assoc < 0 ) {
		write_misses ++;
		//TODO: report cache miss
//		fprintf(stderr, "cache miss during SW!\n" );

		assoc = -1 * (assoc + 1);
		commit_assoc = assoc;
		commit_addr = addra;
		ita.i = 0;
		ita.s[addrb] = value;
		commit_value = ita.i;
		
//		tag_s[idx][assoc] = addr;
//		update_timestamp(idx, assoc);

	} else {
		commit_assoc = -1;
		ita.i = mem_s[idx][assoc];
		ita.s[addrb] = value;
		mem_s[idx][assoc] = ita.i;
	}
}

void mem_SW (unsigned int addr, int value) {
	total_writes ++;
	addr = addr >> 2;
//	if ( addr & 0b11 ) fprintf(stderr, "unaligned SW(%x)?\n", addr );
	int idx = addr % MEM_CACHE_SIZE;
	
	int assoc = get_assoc(addr);
	if ( assoc < 0 ) {
		write_misses ++;
		//TODO: report cache miss
//		fprintf(stderr, "cache miss during SW!\n" );

		assoc = -1 * (assoc + 1);
		commit_assoc = assoc;
		commit_addr = addr;
		commit_value = value;
		
//		tag_s[idx][assoc] = addr;
//		update_timestamp(idx, assoc);

	} else {
		commit_assoc = -1;
		mem_s[idx][assoc] = value;
	}
}

